#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "Socket.hpp"

//图片，首页、资源存储在wwwroot目录
const std::string wwwroot="./wwwroot"; // 这个路径表示当前目录下的wwwroot目录作为web 根目录
const std::string sep = "\r\n";//分隔符
const std::string homepage = "index.html";//首页路径

static const int defaultport = 8082;

class HttpServer;
//线程传参所用结构体
class ThreadData
{
public:
    ThreadData(int fd,HttpServer *s)
    :sockfd(fd),svr(s)
    {

    }
public:
    int sockfd;
    HttpServer *svr;
};

class HttpRequest
{
public:
    //反序列化
    void Deserialize(std::string req)
    {
        while(true)
        {
            std::size_t pos = req.find(sep);//查找分隔符
            if(pos == std::string::npos) break;//说明没找到分隔符，没有这个位置
            //找到的第一组/r/n，这组分隔符前面的字符串一定是请求行或响应行的内容
            //对请求行或响应行的字符串做切割
            std::string temp = req.substr(0, pos);
            //处理报头和有效载荷的空行
            if(temp.empty()) break; //因为在while循环，一直截取，直到temp存储了空行，是空行就break
            req_header.push_back(temp);//把报头里一行一行的内容push到vector容器里
            //把已经push入vector的行在一大串的字符串中移除掉
            req.erase(0, pos+sep.size());//把末尾的分隔符也移除掉
        }
        //经过while循环处理后 req的请求行/相应行+报头已经被去除，剩下的全是报文，把报文给text
        text = req;
    }
    //.png:image/png 只解析请求行或响应行用的函数
    void Parse()
    {
        std::stringstream ss(req_header[0]);
        ss >> method >> url >> http_version;
        file_path=wwwroot;

        if(url=="/"|| url =="/index.html")
        {
            file_path+="/";
            file_path+=homepage;
        }
        else file_path+=url;

        auto pos=file_path.rfind(".");
        if(pos == std::string::npos) suffix=".html";
        else suffix=file_path.substr(pos);
    }

    void DebugPrint()
    {
        for(auto &line:req_header)
        {
            std::cout << "--------------------------------" << std::endl;
            std::cout<<line<<"\r\n";
        }
        std::cout << "method: " << method << std::endl;
        std::cout << "url: " << url << std::endl;
        std::cout << "http_version: " << http_version << std::endl;
        std::cout << "file_path: " << file_path << std::endl;
        std::cout << text << std::endl;
    }
public:
    std::vector<std::string> req_header;//收容报头的一行一行属性的容器
    std::string text;

    // 解析之后的结果,提取请求行的具体信息
    std::string method;
    std::string url;
    std::string http_version;//上面这3个变量要的资源全在请求行，也就是说，全在vector容器的第0个元素位置
    std::string file_path; // ./wwwroot/a/b/c.html 2.png这个变量用来保存资源的路径

    std::string suffix;//表示文件后缀
};

class HttpServer
{
public:
    HttpServer(uint16_t port=defaultport):port_(port)
    {
        content_type.insert({".html","test/html"});
        content_type.insert({".png","image/png"});
    }

    bool Start()
    {
        listensock_.Socket();//创建套接字
        listensock_.Bind(port_);
        listensock_.Listen();
        for(;;)
        {
            std::string clientip;
            uint16_t clientport;
            //接受客户端发来的信息，并把相关数值通过输出型参数得到
            int sockfd=listensock_.Accept(&clientip,&clientport);
            if (sockfd < 0)
                continue;
            std::cout<<"获取到一个新的连接"<<std::endl;
            std::cout<<"该获取的新的连接，它的来源Ip地址为: "<<clientip<<std::endl;
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this);
            pthread_create(&tid, nullptr, ThreadRun, td);//创建一个线程，由该线程去执行发送信息和接收信息的逻辑 
        }
    }

    std::string SuffixToDesc(const std::string &suffix)
    {
        auto iter =content_type.find(suffix);
        if(iter == content_type.end())
            return content_type[".html"];
        else 
            return content_type[suffix];
    }
    //根据传入的路径参数读取具体路径的资源
    static std::string ReadHtmlContent(const std::string &htmlpath)
    {
        std::ifstream in(htmlpath, std::ios::binary);//以二进制方式读取一个文件，通常用于音视频
        if(!in.is_open()) return "";//如果打开失败
        in.seekg(0, std::ios_base::end);//设置输入流的读取位置，第一个参数0表示从文件开头计算，第二个参数表示读到末尾
        auto len = in.tellg();
        in.seekg(0, std::ios_base::beg);//再把读取文件的位置定位到文件最开始

        std::string content;//用来你读取到的二进制文件的内容变量
        content.resize(len);//先把变量的大小设置为len 

        in.read((char*)content.c_str(), content.size());

        in.close();
        return content;
    }
    //读取前端页面
    void HandlerHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);//读取客户端信息
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<buffer<<std::endl;//假设读取到完整的报文

            HttpRequest req;
            req.Deserialize(buffer);//把报头的一行行信息存入容器里
            req.Parse();//解析容器第一个元素（请求行），获得url、path路径等信息
            req.DebugPrint();//打印报头所有信息
            
            //返回相应的过程
            std::string text;//存储有效载荷变量
            bool ok=true;
            text=ReadHtmlContent(req.file_path);//根据路径读取到对应的资源

            if(text.empty())
            {
                ok=false;
                std::string err_html=wwwroot;//先赋值根目录，具体错误页面后面拼接
                err_html +="/";
                err_html+="err_html";//拼接404页面路径
                text=ReadHtmlContent(err_html);//根据路径读取对应的404页面资源
            }

            //状态码描述
            std::string response_line;//存储响应行变量
            if(ok) 
                response_line="HTTP/1.0 200 OK\r\n";
            else
                response_line = "HTTP/1.0 404 Not Found\r\n";
        
            //存储报头属性
            std::string response_header ="Content-Length:";
            response_header += std::to_string(text.size()); // Content-Length: 11
            response_header += "\r\n";
            response_header += "Content-Type: ";//是text还是html，有些老的浏览器不知道，需要显式的告知它
            response_header += SuffixToDesc(req.suffix);
            response_header += "\r\n";
            response_header += "Set-Cookie: name=haha";
            response_header += "\r\n";
        
            std::string blank_line = "\r\n";//分离报头和有效载荷的空行
            //存储整个信息
            std::string response = response_line;//相应行
            response += response_header;//报头
            response += blank_line;//空行
            response += text;//有效载荷（即正文信息）

            //向客户端返回信息（即服务端向客户端发送响应）
            send(sockfd,response.c_str(),response.size(),0);
        }
        close(sockfd);
    }

    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());//设置线程的分离状态
        ThreadData *td = static_cast<ThreadData *>(args);
        td->svr->HandlerHttp(td->sockfd);
        //close(td->sockfd);
        delete td;
        return nullptr;
    }
private:
    Sock listensock_;
    uint16_t port_;
    std::unordered_map<std::string,std::string> content_type;
};